/*
 * application.cpp
 *
 *  Created on: 2009-1-6
 *      Author: fanliangliang
 */

#include "application.h"
#include "xml_config.h"
#include "sms_appender.h"
#include "job_generator.h"
#include "compressor.h"
#include "file_sender.h"
#include "time_set.h"
#include "utility.h"

using namespace toxic;
using namespace std;

Application::Application()
:generator_(0)
,compressor_(0)
,sender_(0)
,isRunning_(false) {

}

Application::~Application() {
	stop();

	typedef NameRuleMap::value_type value_type;
	foreach (value_type value, rules_) {
		delete value.second;
	}

	foreach (Item* item, items_) {
		delete item;
	}
}

void Application::load(const string& xmlfile) {
	XmlConfig config(xmlfile);
	loadLog(config);
	loadRules(config);
	loadItems(config);
}

void Application::start() {
	if (!isRunning_) {
		startLog();
		startWorkerThread();
		isRunning_ = true;
	}
}

void Application::stop() {
	if (isRunning_) {
		stopWorkerThread();
		stopLog();
		isRunning_ = false;
	}
}

static Log::Level stringToLevel(const string& level) {
	static const char* levelTable[] = {
		"FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"
	};

	for (size_t i = 0; i < dimof(levelTable); ++i) {
		if (levelTable[i] == level) {
			return Log::Level(i);
		}
	}
	return Log::FATAL;
}

void Application::loadLog(XmlConfig& config) {
	config.setRoot("/config/log/");
	level_ = stringToLevel(config.read<string>("level"));

	size_t numAppender = config.count("appender");

	for (size_t i = 1; i <= numAppender; ++i) {
		string type = config.readf<string>("appender[%d]/@type", i);
		if (type == "console") {
			string filename = config.readf<string>("appender[%d]/target", i);
			if (filename == "stdout") {
				appenders_.push_back(new ConsoleAppender(cout));
			}
			else if (filename == "stderr") {
				appenders_.push_back(new ConsoleAppender(cerr));
			}
			else if (filename == "stdlog") {
				appenders_.push_back(new ConsoleAppender(clog));
			}
			else {
				// TODO: unknown console target
			}
		}
		else if (type == "file") {
			string filename = config.readf<string>("appender[%d]/filename", i);
			appenders_.push_back(new FileAppender(filename.c_str()));
		}
		else if (type == "socket") {
			string protocol = config.readf<string>("appender[%d]/protocol", i);
			string server = config.readf<string>("appender[%d]/server", i);
			uint16_t port = config.readf<uint16_t>("appender[%d]/port", i);

			appenders_.push_back(new SocketAppender(protocol, server, port));
		}
		else if (type == "sms") {
			Log::Level warnLevel = stringToLevel(config.readf<string>("appender[%d]/level", i));
			string smsInterface = config.readf<string>("appender[%d]/smsInterface", i);
			string server = config.readf<string>("appender[%d]/server", i);
			uint16_t port = config.readf<uint16_t>("appender[%d]/port", i);

			vector<string> phoneNumbers;
			config.readf(phoneNumbers, "appender[%d]/phoneNumber", i);

			MessageService service;
			service.setSmsInterface(smsInterface);
			service.setServer(server);
			service.setPort(port);

			SmsAppender* app = new SmsAppender();
			app->setWarnLevel(warnLevel);
			app->setMessageService(service);
			app->addPhoneNumber(phoneNumbers);

			appenders_.push_back(app);
		}
		else {
			// TODO: unknown appender type
		}
	}
}

void Application::loadRules(XmlConfig& config) {
	config.setRoot("/config/");
	size_t numRule = config.count("rule");
	for (size_t i = 1; i <= numRule; ++i) {
		Rule* rule = new Rule();

		rule->name = config.readf<string>("rule[%d]/@name", i);
		string destination = config.readf<string>("rule[%d]/destination", i);

		toxic::replace(destination, '\\', '/');
		if (destination[destination.size() - 1] != '/') {
			destination += '/';
		}
		rule->destination = destination;

		size_t numSource = config.countf("rule[%d]/source", i);
		for (size_t j = 1; j <= numSource; ++j) {
			Source source;
			source.compress = config.readf<bool>("rule[%d]/source[%d]/@compress", i, j);
			string pathname = config.readf<string>("rule[%d]/source[%d]", i, j);

			toxic::replace(pathname, '\\', '/');
			toxic::rtrim(pathname, "/");
			source.pathname = pathname;

			rule->sources.push_back(source);
		}

		rules_.insert(make_pair(rule->name, rule));
	}
}

void Application::loadItems(XmlConfig& config) {
	config.setRoot("/config/");
	size_t numItem = config.count("item");
	for (size_t i = 1; i <= numItem; ++i) {
		Item* item = new Item();

		string minute = config.readf<string>("item[%d]/@minute", i);
		string hour = config.readf<string>("item[%d]/@hour", i);
		string day = config.readf<string>("item[%d]/@day", i);
		string month = config.readf<string>("item[%d]/@month", i);
		string week = config.readf<string>("item[%d]/@week", i);

		TimeSet* timeset = new TimeSet();
		timeset->setMinute(minute);
		timeset->setHour(hour);
		timeset->setDay(day);
		timeset->setMonth(month);
		timeset->setWeek(week);

		item->timeset = timeset;

		size_t numAction = config.countf("item[%d]/action", i);
		for (size_t j = 1; j <= numAction; ++j) {
			Action* action = new Action();
			string ruleName = config.readf<string>("item[%d]/action[%d]/@rule", i, j);

			NameRuleMap::const_iterator it = rules_.find(ruleName);
			if (it != rules_.end()) {
				action->rule = *it->second;
			}

			config.readf(action->receivers, "item[%d]/action[%d]/receiver", i, j);
			item->actions.push_back(action);
		}

		items_.push_back(item);
	}
}

void Application::startLog() {
	xlog.setLevel(level_);
	foreach (Appender* app, appenders_) {
		xlog.addAppender(app);
	}
}

void Application::stopLog() {
	// it is better to shutdown manually
	xlog.shutdown();
}

static void deleteHandler(const string& filename) {
	deleteFile(filename);
}

void Application::startWorkerThread() {
	sender_ = new FileSender();
	sender_->setFileDeleteHandler(&deleteHandler);
	compressor_ = new Compressor(sender_);
	generator_ = new JobGenerator(compressor_, &items_);

	sender_->start();
	compressor_->start();
	generator_->start();
}

void Application::stopWorkerThread() {
	// stop in reverse order
	generator_->stop();
	generator_->join();

	compressor_->stop();
	compressor_->join();

	sender_->stop();
	sender_->join();

	delete generator_;
	delete compressor_;
	delete sender_;
}

ostream& Application::print(ostream& out) const {
	typedef NameRuleMap::value_type value_type;
	foreach (value_type value, rules_) {
		cout << "rule:\n" << *value.second << endl;
	}

	foreach (Item* item, items_) {
		cout << "item:\n" << *item << endl;
	}
	return out;
}

ostream& operator<<(ostream& out, const Application& app) {
	return app.print(out);
}
